<script type="jsx">
import { excludeKeys } from '@/utils/index';

function translateFieldsLabel(fields, trans) {
  const TRANSLATE_KEYS = [
    'placeholder',
    'label',
    'startPlaceholder',
    'endPlaceholder'
  ];
  const isTranslateKey = k => TRANSLATE_KEYS.some(s => s === k);
  const isReplaceKey = k => TRANSLATE_KEYS.some(s => `_${s}` === k);
  const transform = t => {
    if (Array.isArray(t)) {
      return t.map(transform);
    }
    if (t && typeof t === 'object') {
      return Object.keys(t).reduce((o, k) => {
        if (isTranslateKey(k)) {
          return { ...o, [k]: trans(t[k]) };
        }
        if (isReplaceKey(k)) {
          return { ...o, [k.replace(/^_/, '')]: t[k] };
        }
        return { ...o, [k]: transform(t[k]) };
      }, t);
    }
    return t;
  };
  return transform(fields);
}

function makeGetObjectProp(k) {
  const isValidObject = t => t && typeof t === 'object';
  return function getObjectValue(o) {
    if (!isValidObject(o)) return {};
    const value = o[k];
    if (!isValidObject(value)) return {};
    return value;
  };
}

const getAttrs = makeGetObjectProp('attrs');
const getFormAttrs = makeGetObjectProp('formAttrs');

export default {
  props: {
    value: {
      type: Object,
      required: true
    },
    fields: {
      type: Array,
      default: () => []
    },
    size: {
      type: String,
      default: ''
    },
    shouldTranslateFields: {
      type: Boolean,
      default: true
    }
  },

  computed: {
    translatedFields() {
      if (this.shouldTranslateFields) {
        return translateFieldsLabel(this.fields, s => this.$t(s));
      }
      return this.fields;
    }
  },

  methods: {
    update(field, value) {
      this.$emit('input', { ...this.value, [field]: value });
    },

    renderChildren(children) {
      if (!children) return null;
      return children.map(this.renderField).filter(t => t);
    },

    renderInput(item) {
      const value = this.value[item.field];
      return (
        <el-input
          size={this.size}
          slot={item.slot}
          value={value}
          placeholder={item.placeholder || this.$t('please_enter')}
          {...{ attrs: getAttrs(item) }}
          onInput={v => this.update(item.field, v)}
        >
          {this.renderChildren(item.children)}
        </el-input>
      );
    },

    renderSelect(item) {
      const value = this.value[item.field];
      const renderOptions = t => {
        if (Array.isArray(t.options)) {
          return t.options.map(option => (
            <el-option
              key={option.value}
              label={option.label}
              value={option.value}
            />
          ));
        }
        return null;
      };

      return (
        <el-select
          size={this.size}
          slot={item.slot}
          value={value}
          placeholder={item.placeholder || this.$t('please_select_')}
          {...{ attrs: getAttrs(item) }}
          onInput={v => this.update(item.field, v)}
        >
          {renderOptions(item)}
        </el-select>
      );
    },

    renderDate(item) {
      const value = this.value[item.field];
      return (
        <el-date-picker
          size={this.size}
          slot={item.slot}
          value={value}
          picker-options={item.pickerOptions}
          placeholder={item.placeholder || this.$t('please_select_')}
          type={'date'}
          {...{ attrs: getAttrs(item) }}
          onInput={v => this.update(item.field, v)}
        />
      );
    },

    renderDateRange(item) {
      const value = this.value[item.field];
      return (
        <el-date-picker
          size={this.size}
          slot={item.slot}
          value={value}
          picker-options={item.pickerOptions}
          placeholder={item.placeholder || this.$t('please_select_')}
          type={'daterange'}
          {...{ attrs: getAttrs(item) }}
          onInput={v => this.update(item.field, v)}
        />
      );
    },

    renderInputNumber(item) {
      const value = this.value[item.field];
      return (
        <el-input-number
          size={this.size}
          slot={item.slot}
          value={value}
          placeholder={item.placeholder || this.$t('please_enter')}
          {...{ attrs: getAttrs(item) }}
          onInput={v => this.update(item.field, v)}
        >
          {this.renderChildren(item.children)}
        </el-input-number>
      );
    },

    renderField(item) {
      switch (item.type) {
        case 'input': {
          return this.renderInput(item);
        }
        case 'input-number': {
          return this.renderInputNumber(item);
        }
        case 'select': {
          return this.renderSelect(item);
        }
        case 'date': {
          return this.renderDate(item);
        }
        case 'daterange': {
          return this.renderDateRange(item);
        }
        default: {
          return null;
        }
      }
    },

    renderFormItem(item) {
      const field = this.renderField(item);
      if (!field) return null;
      const label = item.label ? `${item.label}:` : '';
      return (
        <el-form-item label={label} {...{ attrs: getFormAttrs(item) }}>
          {field}
        </el-form-item>
      );
    }
  },

  render() {
    const attrs = {
      inline: true,
      ...excludeKeys(this.$attrs, ['value', 'fields'])
    };
    return (
      <el-form {...{ attrs }}>
        {this.$slots.prepend}
        {this.translatedFields.map(this.renderFormItem)}
        {this.$slots.default}
        {this.$slots.append}
      </el-form>
    );
  }
};
</script>
